package com.ruoyi.blockchain;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

/**
 * 区块类 - 区块链的基本组成单元
 * 每个区块包含区块头和区块体两部分
 */
public class Block {
    // 区块头字段
    private long index;             // 区块索引/高度
    private long timestamp;         // 时间戳（区块创建时间）
    private String previousHash;    // 前一个区块的哈希值
    private String hash;            // 当前区块的哈希值
    private long nonce;             // 工作量证明的随机数
    private int difficulty;         // 挖矿难度
    
    // 区块体字段
    private List<Transaction> transactions; // 交易列表

    /**
     * 构造函数
     * @param index 区块索引
     * @param previousHash 前一个区块的哈希值
     * @param difficulty 挖矿难度
     * @param transactions 交易列表
     */
    public Block(long index, String previousHash, int difficulty, List<Transaction> transactions) {
        this.index = index;
        this.timestamp = Instant.now().getEpochSecond();
        this.previousHash = previousHash;
        this.difficulty = difficulty;
        this.transactions = transactions != null ? transactions : new ArrayList<>();
        this.nonce = 0;
        this.hash = calculateHash();
    }
    
    /**
     * 构造函数 - 兼容Blockchain类的使用方式
     * @param index 区块索引
     * @param transactions 交易列表
     * @param previousHash 前一个区块的哈希值
     */
    public Block(long index, List<Transaction> transactions, String previousHash) {
        this.index = index;
        this.timestamp = Instant.now().getEpochSecond();
        this.previousHash = previousHash;
        this.difficulty = 1; // 默认难度
        this.transactions = transactions != null ? transactions : new ArrayList<>();
        this.nonce = 0;
        this.hash = calculateHash();
    }

    /**
     * 计算区块的哈希值
     * 使用SHA-256算法，基于区块头和区块体的所有数据
     * @return 区块的哈希值
     */
    public String calculateHash() {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            
            String dataToHash = index + previousHash + timestamp + nonce + difficulty + getMerkleRoot();
            byte[] hashBytes = digest.digest(dataToHash.getBytes());
            
            StringBuilder hashStringBuilder = new StringBuilder();
            for (byte b : hashBytes) {
                hashStringBuilder.append(String.format("%02x", b));
            }
            
            return hashStringBuilder.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256算法不可用", e);
        }
    }

    /**
     * 挖矿方法 - 实现工作量证明(PoW)机制
     * 不断尝试不同的nonce值，直到找到满足难度要求的哈希值
     */
    public void mineBlock() {
        // 创建目标字符串，包含 difficulty 个前导零
        String target = new String(new char[difficulty]).replace('\0', '0');
        
        // 不断计算哈希，直到满足难度要求
        while (!hash.substring(0, difficulty).equals(target)) {
            nonce++;
            hash = calculateHash();
        }
        
        System.out.println("区块已挖出! 哈希值: " + hash + ", Nonce: " + nonce);
    }
    
    /**
     * 挖矿方法重载 - 支持指定难度
     * @param difficulty 挖矿难度
     */
    public void mineBlock(int difficulty) {
        this.difficulty = difficulty;
        mineBlock();
    }

    /**
     * 计算默克尔树根
     * 默克尔树用于验证区块中交易的完整性
     * @return 默克尔树根哈希
     */
    private String getMerkleRoot() {
        if (transactions.isEmpty()) {
            return "0"; // 空交易列表的默克尔根
        }
        
        List<String> transactionHashes = new ArrayList<>();
        for (Transaction tx : transactions) {
            transactionHashes.add(tx.calculateHash());
        }
        
        return buildMerkleTree(transactionHashes);
    }

    /**
     * 构建默克尔树
     * @param hashes 当前层级的哈希列表
     * @return 默克尔树根哈希
     */
    private String buildMerkleTree(List<String> hashes) {
        if (hashes.size() == 1) {
            return hashes.get(0);
        }
        
        List<String> newLevelHashes = new ArrayList<>();
        
        // 处理每一对哈希
        for (int i = 0; i < hashes.size(); i += 2) {
            if (i + 1 < hashes.size()) {
                // 合并两个哈希并计算新哈希
                newLevelHashes.add(hashTwoStrings(hashes.get(i), hashes.get(i + 1)));
            } else {
                // 如果是奇数个哈希，复制最后一个哈希
                newLevelHashes.add(hashTwoStrings(hashes.get(i), hashes.get(i)));
            }
        }
        
        // 递归构建上层树
        return buildMerkleTree(newLevelHashes);
    }

    /**
     * 合并两个字符串并计算哈希
     * @param a 第一个字符串
     * @param b 第二个字符串
     * @return 合并后的哈希值
     */
    private String hashTwoStrings(String a, String b) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hashBytes = digest.digest((a + b).getBytes());
            
            StringBuilder hashStringBuilder = new StringBuilder();
            for (byte b1 : hashBytes) {
                hashStringBuilder.append(String.format("%02x", b1));
            }
            
            return hashStringBuilder.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256算法不可用", e);
        }
    }

    // Getters and Setters
    public long getIndex() { return index; }
    public long getTimestamp() { return timestamp; }
    public String getPreviousHash() { return previousHash; }
    public String getHash() { return hash; }
    public long getNonce() { return nonce; }
    public int getDifficulty() { return difficulty; }
    public List<Transaction> getTransactions() { return transactions; }
    
    public void setDifficulty(int difficulty) { this.difficulty = difficulty; }
    public void addTransaction(Transaction transaction) { 
        if (transaction != null && transaction.isValid()) {
            this.transactions.add(transaction);
            // 添加交易后重新计算哈希
            this.hash = calculateHash();
        }
    }
    
    @Override
    public String toString() {
        return "Block{" +
                "index=" + index +
                ", timestamp=" + timestamp +
                ", previousHash='" + previousHash + '\'' +
                ", hash='" + hash + '\'' +
                ", nonce=" + nonce +
                ", difficulty=" + difficulty +
                ", transactionsCount=" + transactions.size() +
                '}';
    }
}